001 /* 002 * Copyright 2006 Stephen J. McConnell. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 013 * implied. 014 * 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package net.dpml.lang; 020 021 import java.io.IOException; 022 import java.io.OutputStream; 023 import java.io.OutputStreamWriter; 024 import java.io.Writer; 025 import java.net.URI; 026 027 import javax.xml.XMLConstants; 028 029 import net.dpml.util.Logger; 030 031 /** 032 * Part datastructure. 033 * 034 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a> 035 * @version 1.0.2 036 */ 037 public abstract class Part 038 { 039 /** 040 * A value encoder. 041 */ 042 protected static final ValueEncoder VALUE_ENCODER = new ValueEncoder(); 043 044 /** 045 * Default XML header. 046 */ 047 protected static final String XML_HEADER = "<?xml version=\"1.0\"?>"; 048 049 /** 050 * Part schema URN. 051 */ 052 protected static final String PART_SCHEMA_URN = "link:xsd:dpml/lang/dpml-part#1.0"; 053 054 /** 055 * Part header. 056 */ 057 protected static final String PART_HEADER = 058 "<part xmlns=\"" 059 + PART_SCHEMA_URN 060 + "\"" 061 + "\n xmlns:xsi=\"" 062 + XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI 063 + "\">"; 064 065 /** 066 * Part footer. 067 */ 068 protected static final String PART_FOOTER = "</part>"; 069 070 private final Info m_info; 071 private final Classpath m_classpath; 072 private final Logger m_logger; 073 074 private transient ClassLoader m_classloader; 075 private transient String m_label; 076 077 /** 078 * Load a part from an external XML source with part caching. 079 * @param uri the external part source 080 * @return the resolved part 081 * @exception IOException of an I/O error occurs 082 */ 083 public static Part load( URI uri ) throws IOException 084 { 085 return load( uri, true ); 086 } 087 088 /** 089 * Load a part from an external XML source. 090 * @param uri the external part source 091 * @param cache the cache policy 092 * @return the resolved part 093 * @exception IOException of an I/O error occurs 094 */ 095 public static Part load( URI uri, boolean cache ) throws IOException 096 { 097 return PartDecoder.getInstance().loadPart( uri, cache ); 098 } 099 100 /** 101 * Creation of a new part datastructure. 102 * @param logger the logging channel 103 * @param info the info descriptor 104 * @param classpath the part classpath definition 105 * @exception IOException if an I/O error occurs 106 */ 107 public Part( Logger logger, Info info, Classpath classpath ) throws IOException 108 { 109 this( logger, info, classpath, null ); 110 } 111 112 /** 113 * Creation of a new part datastructure. 114 * @param logger the logging channel 115 * @param info the info descriptor 116 * @param classpath the part classpath definition 117 * @param label debug label 118 * @exception IOException if an I/O error occurs 119 */ 120 public Part( Logger logger, Info info, Classpath classpath, String label ) throws IOException 121 { 122 super(); 123 if( null == info ) 124 { 125 throw new NullPointerException( "info" ); 126 } 127 if( null == classpath ) 128 { 129 throw new NullPointerException( "classpath" ); 130 } 131 m_logger = logger; 132 m_info = info; 133 m_classpath = classpath; 134 m_label = label; 135 } 136 137 /** 138 * Return the default part content. 139 * @return the result of part instantiation 140 * @exception IOException if an IO error occurs 141 */ 142 public Object getContent() throws IOException 143 { 144 try 145 { 146 return instantiate( new Object[0] ); 147 } 148 catch( IOException e ) 149 { 150 throw e; 151 } 152 catch( Exception e ) 153 { 154 final String error = 155 "Part instantiation error."; 156 throw new PartException( error, e ); 157 } 158 } 159 160 /** 161 * Return the part content or null if the result type is unresolvable 162 * relative to the supplied classes argument. 163 * @param classes the content type selection classes 164 * @return the content 165 * @exception IOException if an IO error occurs 166 */ 167 public Object getContent( Class[] classes ) throws IOException 168 { 169 if( classes.length == 0 ) 170 { 171 return getContent(); 172 } 173 else 174 { 175 for( int i=0; i<classes.length; i++ ) 176 { 177 Class c = classes[i]; 178 Object content = getContent( c ); 179 if( null != content ) 180 { 181 return content; 182 } 183 } 184 return null; 185 } 186 } 187 188 /** 189 * Return the part content or null if the result type is unresolvable 190 * relative to the supplied classes argument. Recognized class arguments 191 * include Info, Classpath, Part, ClassLoader, and Object. 192 * 193 * @param c the content type class 194 * @return the content 195 * @exception IOException if an IO error occurs 196 */ 197 protected Object getContent( Class c ) throws IOException 198 { 199 if( Info.class.isAssignableFrom( c ) ) 200 { 201 return getInfo(); 202 } 203 else if( Classpath.class.isAssignableFrom( c ) ) 204 { 205 return getClasspath(); 206 } 207 else if( Part.class.isAssignableFrom( c ) ) 208 { 209 return this; 210 } 211 else if( ClassLoader.class.isAssignableFrom( c ) ) 212 { 213 return getClassLoader(); 214 } 215 else if( Object.class == c ) 216 { 217 try 218 { 219 return instantiate( new Object[0] ); 220 } 221 catch( IOException e ) 222 { 223 throw e; 224 } 225 catch( Exception e ) 226 { 227 final String error = 228 "Part instantiation error."; 229 throw new PartException( error, e ); 230 } 231 } 232 else 233 { 234 return null; 235 } 236 } 237 238 /** 239 * Get the part info descriptor. 240 * 241 * @return the part info datastructure 242 */ 243 public Info getInfo() 244 { 245 return m_info; 246 } 247 248 /** 249 * Get the part classpath definition. 250 * 251 * @return the classpath definition 252 */ 253 public Classpath getClasspath() 254 { 255 return m_classpath; 256 } 257 258 /** 259 * Instantiate a value. 260 * @param args supplimentary arguments 261 * @return the resolved instance 262 * @exception Exception if a deployment error occurs 263 */ 264 public abstract Object instantiate( Object[] args ) throws Exception; 265 266 /** 267 * Externalize the part to XML. 268 * @param output the output stream 269 * @exception IOException if an I/O error occurs 270 */ 271 public void encode( OutputStream output ) throws IOException 272 { 273 final Writer writer = new OutputStreamWriter( output ); 274 writer.write( XML_HEADER ); 275 writer.write( "\n" ); 276 writer.write( "\n" + PART_HEADER ); 277 writer.write( "\n" ); 278 encodeInfo( writer, getInfo() ); 279 writer.write( "\n" ); 280 encodeStrategy( writer, " " ); 281 writer.write( "\n" ); 282 encodeClasspath( writer, getClasspath() ); 283 writer.write( "\n" ); 284 writer.write( "\n" + PART_FOOTER ); 285 writer.write( "\n" ); 286 writer.flush(); 287 output.close(); 288 } 289 290 /** 291 * Test is this part is equiovalent to the supplied part. 292 * 293 * @param other the other object 294 * @return true if the parts are equivalent 295 */ 296 public boolean equals( Object other ) 297 { 298 if( other instanceof Part ) 299 { 300 Part part = (Part) other; 301 if( !m_info.equals( part.getInfo() ) ) 302 { 303 return false; 304 } 305 else 306 { 307 return m_classpath.equals( part.getClasspath() ); 308 } 309 } 310 else 311 { 312 return false; 313 } 314 } 315 316 /** 317 * Get the part hashcode. 318 * 319 * @return the hash value 320 */ 321 public int hashCode() 322 { 323 int hash = m_info.hashCode(); 324 hash ^= m_classpath.hashCode(); 325 return hash; 326 } 327 328 /** 329 * Encode this part strategy to XML. 330 * 331 * @param writer the output stream writer 332 * @param pad the character offset 333 * @exception IOException if an I/O error occurs during part externalization 334 */ 335 protected abstract void encodeStrategy( Writer writer, String pad ) throws IOException; 336 337 /** 338 * Get the implementation classloader. 339 * @return the resolved classloader 340 */ 341 public ClassLoader getClassLoader() 342 { 343 if( null == m_classloader ) 344 { 345 m_classloader = setupClassLoader(); 346 } 347 return m_classloader; 348 } 349 350 /** 351 * Get the assigned logging channel. 352 * @return the logging channel 353 */ 354 protected Logger getLogger() 355 { 356 return m_logger; 357 } 358 359 private ClassLoader setupClassLoader() 360 { 361 try 362 { 363 return buildClassLoader( m_label ); 364 } 365 catch( Exception e ) 366 { 367 final String error = 368 "Classloader build error."; 369 throw new PartError( error, e ); 370 } 371 } 372 373 private ClassLoader buildClassLoader( String label ) throws IOException 374 { 375 ClassLoader base = getAnchorClassLoader(); 376 Classpath classpath = getClasspath(); 377 String tag = getLabel( label ); 378 return newClassLoader( base, classpath, tag ); 379 } 380 381 private String getLabel( String label ) 382 { 383 if( null != label ) 384 { 385 return label; 386 } 387 if( null != getInfo().getTitle() ) 388 { 389 return getInfo().getTitle(); 390 } 391 else 392 { 393 return PartDecoder.getPartSpec( getInfo().getURI() ); 394 } 395 } 396 397 private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label ) throws IOException 398 { 399 return newClassLoader( base, classpath, label, true ); 400 } 401 402 private ClassLoader newClassLoader( ClassLoader base, Classpath classpath, String label, boolean expand ) throws IOException 403 { 404 Logger logger = getLogger(); 405 406 if( expand ) 407 { 408 Classpath cp = classpath.getBaseClasspath(); 409 if( null != cp ) 410 { 411 ClassLoader cl = newClassLoader( base, cp, label + " (super)" ); 412 return newClassLoader( cl, classpath, label, false ); 413 } 414 } 415 416 URI[] uris = classpath.getDependencies( Category.SYSTEM ); 417 if( uris.length > 0 ) 418 { 419 updateSystemClassLoader( uris ); 420 } 421 422 URI[] apis = classpath.getDependencies( Category.PUBLIC ); 423 ClassLoader api = StandardClassLoader.buildClassLoader( logger, label, Category.PUBLIC, base, apis ); 424 URI[] spis = classpath.getDependencies( Category.PROTECTED ); 425 ClassLoader spi = StandardClassLoader.buildClassLoader( logger, label, Category.PROTECTED, api, spis ); 426 URI[] imps = classpath.getDependencies( Category.PRIVATE ); 427 return StandardClassLoader.buildClassLoader( logger, label, Category.PRIVATE, spi, imps ); 428 } 429 430 private ClassLoader getAnchorClassLoader() 431 { 432 ClassLoader context = Thread.currentThread().getContextClassLoader(); 433 if( null != context ) 434 { 435 return context; 436 } 437 else 438 { 439 return Part.class.getClassLoader(); 440 } 441 } 442 443 private void updateSystemClassLoader( URI[] uris ) throws IOException 444 { 445 ClassLoader parent = ClassLoader.getSystemClassLoader(); 446 synchronized( parent ) 447 { 448 if( parent instanceof SystemClassLoader ) 449 { 450 SystemClassLoader loader = (SystemClassLoader) parent; 451 loader.addDelegates( uris ); 452 systemExpanded( uris ); 453 } 454 else 455 { 456 final String message = 457 "Cannot load [" 458 + uris.length 459 + "] system artifacts into a foreign system classloader."; 460 getLogger().debug( message ); 461 } 462 } 463 } 464 465 /** 466 * Handle notification of system classloader expansion. 467 * @param uris the array of uris added to the system classloader 468 */ 469 private void systemExpanded( URI[] uris ) 470 { 471 if( getLogger().isDebugEnabled() ) 472 { 473 StringBuffer buffer = new StringBuffer(); 474 buffer.append( "system classloader expansion" ); 475 for( int i=0; i<uris.length; i++ ) 476 { 477 int n = i+1; 478 buffer.append( "\n [" + n + "] \t" + uris[i] ); 479 } 480 getLogger().debug( buffer.toString() ); 481 } 482 } 483 484 private void encodeInfo( Writer writer, Info info ) throws IOException 485 { 486 String title = info.getTitle(); 487 String description = info.getDescription(); 488 if( null == description ) 489 { 490 if( null == title ) 491 { 492 writer.write( "\n <info/>" ); 493 } 494 else 495 { 496 writer.write( "\n <info title=\"" + title + "\"/>" ); 497 } 498 } 499 else 500 { 501 if( null == title ) 502 { 503 writer.write( "\n <info>" ); 504 } 505 else 506 { 507 writer.write( "\n <info title=\"" + title + "\">" ); 508 } 509 writer.write( "\n <description>" + description + "</description>" ); 510 writer.write( "\n </info>" ); 511 } 512 } 513 514 private void encodeClasspath( Writer writer, Classpath classpath ) throws IOException 515 { 516 writer.write( "\n <classpath>" ); 517 encodeClasspathCategory( writer, classpath, Category.SYSTEM ); 518 encodeClasspathCategory( writer, classpath, Category.PUBLIC ); 519 encodeClasspathCategory( writer, classpath, Category.PROTECTED ); 520 encodeClasspathCategory( writer, classpath, Category.PRIVATE ); 521 writer.write( "\n </classpath>" ); 522 } 523 524 private void encodeClasspathCategory( 525 Writer writer, Classpath classpath, Category category ) throws IOException 526 { 527 URI[] uris = classpath.getDependencies( category ); 528 if( uris.length > 0 ) 529 { 530 String name = category.getName(); 531 writer.write( "\n <" + name + ">" ); 532 for( int i=0; i<uris.length; i++ ) 533 { 534 URI uri = uris[i]; 535 writer.write( "\n <uri>" + uri.toASCIIString() + "</uri>" ); 536 } 537 writer.write( "\n </" + name + ">" ); 538 } 539 } 540 }